package tech.heiming.fogedge.service.impl;

import tech.heiming.fogedge.po.others.ChannelDataField;
import tech.heiming.fogedge.po.others.DeviceMessage;
import tech.heiming.fogedge.repository.mapper.DeviceMapper;
import tech.heiming.fogedge.vo.LineChartVO;
import tech.heiming.fogedge.vo.TimingAnalysisForm;
import tech.heiming.fogedge.repository.repo.DeviceMessageRepo;
import tech.heiming.fogedge.service.TimingAnalysisService;
import tech.heiming.fogedge.util.enums.AggregationType;
import tech.heiming.fogedge.util.enums.FieldType;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author zhm
 */
@Slf4j
@Service
public class TimingAnalysisServiceImpl implements TimingAnalysisService {
    @Resource
    private DeviceMapper deviceMapper;

    private DeviceMessageRepo deviceMessageRepo;

    @Autowired
    public TimingAnalysisServiceImpl(DeviceMessageRepo deviceMessageRepo) {
        this.deviceMessageRepo = deviceMessageRepo;
    }

    @Override
    public LineChartVO analysisByLineChart(TimingAnalysisForm form) {
        List<LocalDateTime> timePoints;
        List<LineChartVO.Metric> metrics = new ArrayList<>(form.getMeasurePoints().size());
        try {
            LocalDateTime startTime = form.getStartTime();
            LocalDateTime endTime = form.getEndTime();
            Duration duration = Duration.between(startTime, endTime);
            timePoints = Stream
                    .iterate(startTime, t -> t.plusMinutes(form.getIntervalMinutes()))
                    .limit(duration.toMinutes() / form.getIntervalMinutes() + 1)
                    .filter(t -> (t.isAfter(startTime) || t.isEqual(startTime)) && t.isBefore(endTime))
                    .collect(Collectors.toList());
            for (TimingAnalysisForm.MeasurePoint measurePoint : form.getMeasurePoints()) {
                LineChartVO.Metric metric = analysisMeasurePoint(measurePoint,
                        form.getIntervalMinutes(), timePoints.size(), startTime, endTime);
                if (metric == null) {
                    metric = new LineChartVO.Metric(measurePoint, Arrays.asList(new Double[timePoints.size()]));
                }
                metrics.add(metric);
            }
        } catch (Exception e) {
            log.error("Server Error: analysisByLineChart(" + form.toString() + ")");
            e.printStackTrace();
            return null;
        }
        return new LineChartVO(timePoints, metrics);
    }

    private LineChartVO.Metric analysisMeasurePoint(TimingAnalysisForm.MeasurePoint measurePoint, Long intervalMinutes, Integer size,
                                                    LocalDateTime startTime, LocalDateTime endTime) {
        String topic = measurePoint.getChannelId().toString();
        // Step 1: 获取数据
        List<DeviceMessage> deviceMessages = deviceMessageRepo
                .getMsgByTopicAndTimeInterval(topic, startTime, endTime);
        if (deviceMessages == null || deviceMessages.size() == 0) {
            return null;
        }
        // Step 2: 根据时间聚合
        ChannelDataField field = deviceMapper.getChannelDataFieldById(measurePoint.getFieldId());
        if (field == null || FieldType.valueOf(field.getFieldType()) != FieldType.DECIMAL) {
            return null;
        }
        Map<Long, List<DeviceMessage>> timedMsgs = deviceMessages.stream()
                .collect(Collectors.groupingBy(deviceMessage -> {
                    Duration duration = Duration.between(startTime, deviceMessage.getDate());
                    return duration.toMinutes() / intervalMinutes;
                }));
        //Ste 3: 生成结果
        Double[] values = new Double[size];
        for (int i = 0; i < size; i++) {
            List<DeviceMessage> messages = timedMsgs.get((long) i);
            if (messages == null || messages.size() == 0) {
                values[i] = null;
                continue;
            }
            List<Double> doubleValues = new ArrayList<>(messages.size());
            for (DeviceMessage deviceMessage : messages) {
                Map<String, Object> fieldMap = deviceMessage.getDataMap();
                Object val = fieldMap.get(field.getFieldName());
                if (val == null) {
                    continue;
                }
                if (val.getClass().equals(Integer.class)) {
                    doubleValues.add(((Integer) val).doubleValue());
                } else if (val.getClass().equals(Double.class)) {
                    doubleValues.add((Double) val);
                }
            }
            values[i] = aggregateMsg(doubleValues, measurePoint.getAggregationType());
        }
        return new LineChartVO.Metric(measurePoint, Arrays.asList(values));
    }

    private Double aggregateMsg(List<Double> values, AggregationType type) {
        if (values == null) {
            return null;
        }
        Double result;
        switch (type) {
            case MAX:
                result = values.stream()
                        .max(Double::compareTo)
                        .orElse(null);
                break;
            case MIN:
                result = values.stream()
                        .min(Double::compareTo)
                        .orElse(null);
                break;
            case AVG:
                result = values.stream()
                        .mapToDouble(value -> value)
                        .average()
                        .orElse(0);
                break;
            case SUM:
                result = values.stream()
                        .mapToDouble(value -> value)
                        .sum();
                break;
            case COUNT:
                result = (double) values.size();
                break;
            default:
                return null;
        }
        return result;
    }
}